JITR(Just In Time Registration)でAWS IoT Coreにデバイスや証明書を登録し、APIGatewayでの認証にデバイス証明書を利用する
AWS IoT Coreの「JITR(Just In Time Registration)」の仕組みを利用してThingとデバイス証明書を登録し、APIGatewayでそのデバイス証明書を利用した認証をするところまで実装してみました。
TL;DR
- JITRの仕組みを使って「AWS IoT Core」でデバイス証明書を簡単に管理できる
- APIGatewayの認証でこの証明書を利用することでセキュアに接続できる
- JITRではThingの登録ができないので、そこを実現するためにゴニョゴニョやり方を模索してみました
1.はじめに
APIGatewayにてデバイス証明書での認証をするためには「カスタムドメイン名」が必要です。本記事では用意できている前提で執筆します。
また、本エントリーで言及しきれていない箇所についてはこちらにコードを置いてあるのでご確認ください。
2.本記事の内容について
本記事で最終的に実現することは「TL;DR」にも一部記載している通り、JITRの流れでThingやデバイス証明書を「AWS IoT Core」に登録し、そのデバイス証明書をAPIGatewayの認証に利用するまで、を実際にやってみたものです。
本エントリーでは以下のような作業を実施します。
- 1.CA証明書の登録
- JITR、APIGatewayの認証の両方にて利用する証明書
- JITRで利用するために「AWS IoT Core」に、APIGatewayにて利用するために「S3」にCA証明書を登録する
- 2.必要なAWS側リソースを準備
- ex.AWS IoT Core Policy/Lambda関数
- 3.JITR、デバイス証明書の作成
- 4.APIGatewayへの接続確認
処理のイメージはこんな感じです。
https://docs.aws.amazon.com/ja_jp/iot/latest/developerguide/authorizing-direct-aws.html
3.やってみる
3-1.CA証明書の登録
以下を参考にして「AWS IoT Core」にCA証明書を登録します。
- Just-in-Time Registration of Device Certificates on AWS IoT
- AWS IoT JSON形式のテンプレートを使ってデバイスを自動登録する (Just-in-Time Provisioning)
- AWS IoTでデバイス証明書のジャストインタイム登録をやってみた
# キーペアを生成 openssl genrsa -out ./certs/rootCA.key 2048 # CA証明書を生成 # 値を入れるように促されるところは全てEnterでOK(本リポジトリに保存している「openssl.conf」に情報を事前に記載しておくこと) openssl req -x509 -new -nodes -key ./certs/rootCA.key -sha256 -days 1024 -out ./certs/rootCA.pem -config openssl.conf -extensions v3_ca # AWS IoTから登録コードを取得 CODE=`aws iot get-registration-code --output text` # プライベートキー検証証明書のキーペアを生成 openssl genrsa -out ./certs/verificationCert.key 2048 # プライベートキー検証証明書のCSRを作成 VC_SUBJ="/C=JP/ST=Tokyo/L=Tokyo/O=MyOrg/OU=verification/CN=${CODE}" openssl req -new -key ./certs/verificationCert.key -out ./certs/verificationCert.csr -subj "$VC_SUBJ" # プライベートキー検証証明書を作成 openssl x509 -req -in ./certs/verificationCert.csr -CA ./certs/rootCA.pem -CAkey ./certs/rootCA.key -CAcreateserial -out ./certs/verificationCert.pem -days 500 -sha256 # AWS IoTにCA証明書を登録 aws iot register-ca-certificate \ --ca-certificate file://certs/rootCA.pem \ --verification-cert file://certs/verificationCert.pem \ --set-as-active \ --allow-auto-registration
CA証明書の「証明書の自動登録」を有効化しています。
これで、「このCA証明書を利用したJITRのリクエスト」はデバイス証明書が自動的にAWS IoT Coreに登録されるようになります。
また、APIGatewayでの認証にもこのCA証明書を利用するので、S3バケットにCA証明書を保存しておきます。
3-2.必要なAWS側リソースを準備
今回の検証をするために以下のリソースをデプロイします。
- AWS IoT Core Policy
- JITRとアプリケーション用の2種類
- Lambda関数
- JITR用の2つ、APIGatewayでの認証確認用1つ
- APIGateway
- カスタムドメインも必要
こちらのリポジトリをcloneして、「cdk/.env」で環境変数を変更した後に以下の通りデプロイしてください。
$ yarn $ yarn deploy
3-3.デバイス証明書の作成とJITR
続いて、デバイス証明書を作成します。
CA証明書、鍵を使ってデバイス証明書に署名する必要があります。
# キーペアを生成 openssl genrsa -out ./certs/deviceCert.key 2048 # デバイス証明書のCSRを作成 openssl req -new -key ./certs/deviceCert.key -out ./certs/deviceCert.csr # デバイス証明書を作成 openssl x509 -req -in ./certs/deviceCert.csr -CA ./certs/rootCA.pem -CAkey ./certs/rootCA.key -CAcreateserial -out ./certs/deviceCert.pem -days 500 -sha256
Point!
続いて、デバイス証明書をAWS IoT Coreに登録します。
これはIoTCoreでのJITRのお作法ですが、CA証明書とデバイス証明書を結合してリクエストします。
また、AWSのrootCAを事前に用意しておいてください。
# デバイス証明書とCA証明書を結合 cat ./certs/deviceCert.pem ./certs/rootCA.pem > ./certs/deviceCertAndCACert.crt # JITR開始 curl --tlsv1.2 \ --cacert ./certs/amazonRootCa.cert \ --cert ./certs/deviceCertAndCACert.crt \ --key ./certs/deviceCert.key \ --request POST \ --data "{}" \ "https://{IoTCoreのデータエンドポイント}:8443/topics/jitr/start?qos=1"
初回リクエストは必ず失敗します。(AWSにこの証明書が登録されていないので)
# こんな返信がくる curl: (52) Empty reply from server
AWS側はこのリクエストを受けたら非同期処理を開始します。
以下に本処理について補足しますので、気になる方は以下をクリックしてご参照ください。
ここをクリック
なお、MQTTプロトコルでリクエストしてもいいのですが、以下のように考えた結果HTTPプロトコルでリクエストしました。
- 実行完了がデバイス側で確認できるようにしたい
- MQTTプロトコルで、となるとconnectの権限も付与する必要がある
- しかし、connectを許可するためには「クライアントID: *」か「全デバイス固有のクライアントID」をIoTPolicyで指定する必要がある
- AWS IoT Coreの仕様として、あるクライアントIDで複数の接続が確認された場合は先に接続していた方のコネクションが切断されてしまいトラブルの元になる
- https://docs.aws.amazon.com/ja_jp/iot/latest/developerguide/audit-chk-conflicting-client-ids.html
- connectに「*」か「全デバイスに固有のクライアントID」のいずれを採用するとしても、この問題に遭遇しうる
- 今回はデバイスがAPIGatewayにHTTPリクエストすることが前提なので、HTTPリクエストとした
- MQTTのコネクションの権限不要なので
# AWS側ではJITRに伴いデバイス証明書が登録された際に、「$aws/events/certificates/registered/#」トピックに以下のようなメッセージが発行されます。 { "certificateId": "xxxxxxxxxxxxxxxxxx", "caCertificateId": "yyyyyyyyyyyyyyyyyyy", "timestamp": 1654051712321, "certificateStatus": "PENDING_ACTIVATION", "awsAccountId": "123456789012", "certificateRegistrationTimestamp": null }
今回の実装ではAWS側の処理が完了したらこのリクエストが成功するように権限を付与しているので、デバイス側はこの処理が成功するまで実行し続けます。
# こんな返信が来たらOK {"message":"OK","traceId":"1870a050-b5f0-6fab-36ef-633e6790c64a"}
この処理完了時には、AWS側では以下の状態です。
- デバイス証明書が登録&有効化
- JITRに必要なIoTポリシーをデバイス証明書にアタッチ
- 「jitr/start」、「jitr/request」トピックへのパブリッシュ権限
- アプリケーションに必要なIoTポリシーはまだ付与されていない
続いて以下のリクエストを実行します。
この処理は正常終了します。(上記の通り、デバイス証明書に対してアタッチされているIoTポリシーに必要な権限が付与されているため)
curl --tlsv1.2 \ --cacert ./certs/amazonRootCa.cert \ --cert ./certs/deviceCertAndCACert.crt \ --key ./certs/deviceCert.key \ --request POST \ --data "{\"thingName\": \"{デバイスごとに一意のUUID}\"}" \ "https://{IoTCoreのデータエンドポイント}:8443/topics/jitr/request?qos=1"
# 権限が付与されているので以下のような返信が来る {"message":"OK","traceId":"8f794d1b-895f-9c11-95bd-67aefd83f05f"}
この処理を受けて、AWS側では以下を実施するように実装してあります。
- Thingの作成
- デバイスが送信したUUIDをThingNameに利用する
- Thingとデバイス証明書のアタッチ
- デバイス証明書にアプリケーションに必要なIoTポリシーをアタッチ
- デバイス証明書から不要になったIoTポリシーをデタッチ
上記を先ほどの「jitr/start」へのパブリッシュでまとめて処理しなかった理由は以下の2点です。
- デバイス、AWS側でクライアントIDを連絡するために処理を2段階に分ける
- JITR完了後はJITRに必要な権限は不要。そのためIoT PolicyをJITR用とアプリ用に分けて、アプリ用ポリシーがアタッチできたらJITR用ポリシーをデタッチする
最後に、ここまでの処理が完了していることを確認するために以下のリクエストを実施します。
先ほどの処理でデバイス証明書にアタッチしたIoTポリシーのおかげでパブリッシュできるトピックへのパブリッシュです。
これが正常終了したら、デバイス証明書登録やThingの作成までひと段落です。
mosquitto_pub \ --cafile ./certs/amazonRootCa.cert \ --cert ./certs/deviceCertAndCACert.crt \ --key ./certs/deviceCert.key \ -h {IoTCoreのデータエンドポイント} \ -p 8883 \ -q 1 \ -t things/{デバイスごとに一意のUUID}/confirmation \ -i {デバイスごとに一意のUUID} \ --tls-version tlsv1.2 \ -m "{}" -d
以下のようにコネクト、パブリッシュ成功の返信を受け取ればOK!
Client {デバイスごとに一意のUUID} sending CONNECT Client {デバイスごとに一意のUUID} received CONNACK (0) Client {デバイスごとに一意のUUID} sending PUBLISH (d0, q1, r0, m1, 'things/{デバイスごとに一意のUUID}/confirmation', ... (2 bytes)) Client {デバイスごとに一意のUUID} received PUBACK (Mid: 1, RC:0) Client {デバイスごとに一意のUUID} sending DISCONNECT
これでデバイスはIoTCoreにMQTTでセキュアに接続できます。
3-4.APIGatewayへの接続確認
「1.はじめに」で述べたとおり、APIGatewayでクライアント証明書を使った認証をするためにはカスタムドメインを設定する必要がありますが、本エントリーではこの手順は完了していることを前提として進めます。
カスタムドメインの設定が完了すれば、「3-2.必要なAWS側リソースを準備」で作成したAPIGatewayにて作成したAPIGatewayでデバイス証明書を利用して認証できるようになっています。
今回は認証できていることを確認したいだけなので、APIGatewayの裏には以下のようなシンプルなLambda関数があるだけです。
export const handler = async(): Promise => { try { const res = { statusCode: 200, body: '{"message": "Good."}', }; return res; } catch (error: unknown) { console.log({error}) throw new Error('Failed in handler.'); } };
「デバイス証明書の有無」でそれぞれリクエストしてみます。
# 証明書有でリクエスト curl -i \ --key ./certs/deviceCert.key \ --cert ./certs/deviceCertAndCACert.crt \ -X GET "https://{APIGatewayのカスタムドメイン}/sample"
成功!
{"message": "Good."}
# 証明書無でリクエスト curl -X GET "https://{APIGatewayのカスタムドメイン}/sample"
失敗!
curl: (35) LibreSSL SSL_connect: SSL_ERROR_SYSCALL in connection to {APIGatewayのカスタムドメイン}:443
いい感じですね!
注意
4.まとめ
JITRでデバイス証明書を登録し、その流れでThingを作成しました。また、APIGatewayの認証にデバイス証明書を利用することでデバイスはAPIにセキュアに接続できます。
JITRでは証明書の登録を簡単にできますが、Thing作成やThingNameをデバイスとAWSで連絡する方法については一手間考える必要がありそうです。